package com.bytech.common.utils;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.TemplateConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.util.CollectionUtils;

/**
 * 一个支持多模块应用的MP代码生成器用于自动生成实体类 service和其实现、controller与mapper接口和XML文件
 * MP生成器的功能就是使用你定义的模板或者默认模板，通过表字段生成实体类等等基础的一些类，并将其放到你配置的路径下面，这个路径是自己根据自己的项目结构拼接的
 *
 * @Author zhaolt
 * @Create 2021/7/22 17:28
 */
public class MybatisPlusCodeGenerator {

    private static AutoGenerator generator = null; // MP 代码生成器实例
    private static final List<String> menuList = ImmutableList.of("1.生成生成controller", "2.生成service（包括其实现）", "3.生成mapper（包括xml文件）", "4.只生成实体类"); // 菜单
    private static List<String> userSelections = Lists.newArrayList(); // 用户选择的菜单
    private static final String rootProjectPath = System.getProperty("user.dir"); // 根项目路径
    private static TemplateConfig templateConfig = new TemplateConfig();

    // 设置自定义的模板路径 如果用默认的就这样设置 templateConfig.setController(ConstVal.TEMPLATE_MAPPER);
    private final static String MY_TEMPLATE_MAPPER = "/templates/mpg/mapper.java";
    private final static String MY_TEMPLATE_XML = "/templates/mpg/mapper.xml";
    private final static String MY_TEMPLATE_ENTITY_JAVA = "/templates/mpg/entity.java";
    private final static String MY_TEMPLATE_SERVICE = "/templates/mpg/service.java";
    private final static String MY_TEMPLATE_SERVICE_IMPL = "/templates/mpg/serviceImpl.java";
    private final static String MY_TEMPLATE_CONTROLLER = "/templates/mpg/controller.java";
    private final static String AUTHOR = "Zhao"; // 代码注释中的作者

    static {
        // 初始化生成哪些模板
        templateConfig.setXml(null);
        templateConfig.setEntity(null);
        templateConfig.setEntityKt(null);
        templateConfig.setService(null);
        templateConfig.setController(null);
        templateConfig.setServiceImpl(null);
        templateConfig.setMapper(null);
    }

    public static void main(String[] args) {
        showMainMenu();
        InjectionConfig injectionConfig = new InjectionConfig() {
            //自定义属性注入
            //在.ftl(或者是.vm)模板中，通过${cfg.map的key}获取属性
            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<>();
                map.put("MapperAnnotation", "MyBatisRepository"); // 配合自动扫描使用的 mapper 接口注解
                map.put("MapperAnnotationPackage", "com.bytech.common.annotation.MyBatisRepository"); // 注解的包路径
                this.setMap(map);
            }
        };
        String tableName = readUserInput("请输入需要生成的表名:", true);
        String folderName = NamingStrategy.underlineToCamel(tableName.toLowerCase());
        // 包配置
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.bytech");
        Map<String, String> pathInfo = new HashMap<>(); // 文件输出路径
        if (!CollectionUtils.isEmpty(userSelections)) {
            generator = new AutoGenerator();
            templateConfig.setEntity(MY_TEMPLATE_ENTITY_JAVA); // 开启生成 entity
            packageConfig.setEntity("common.biz.entity." + folderName); // entity 对象包名（entity 对象统一存放于common模块中，不需要另外设置）
            pathInfo.put(ConstVal.ENTITY_PATH, getParentPath("common", packageConfig.getParent(), packageConfig.getEntity()));
            if (!userSelections.contains("4")) {
                for (String selection : userSelections) {
                    switch (selection) {
                        case "1":
                            String controllerModuleName = readUserInput("请输入Controller所属模块名称:", true);
                            packageConfig.setController(controllerModuleName + ".biz.controller." + folderName);
                            pathInfo.put(ConstVal.CONTROLLER_PATH, getParentPath(controllerModuleName, packageConfig.getParent(), packageConfig.getController()));
                            // 开启生成controller代码
                            templateConfig.setController(MY_TEMPLATE_CONTROLLER);
                            break;
                        case "2":
                            String serviceModuleName = readUserInput("请输入Service接口与其实现所属模块名称:", true);
                            // 设置 service 包名和路径
                            packageConfig.setService(serviceModuleName + ".biz.service." + folderName);
                            pathInfo.put(ConstVal.SERVICE_PATH, getParentPath(serviceModuleName, packageConfig.getParent(), packageConfig.getService()));
                            // 设置 serviceImpl 包名和路径
                            packageConfig.setServiceImpl(serviceModuleName + ".biz.service." + folderName + ".Impl");
                            pathInfo.put(ConstVal.SERVICE_IMPL_PATH, getParentPath(serviceModuleName, packageConfig.getParent(), packageConfig.getServiceImpl()));
                            // 开启生成 service和其实现
                            templateConfig.setService(MY_TEMPLATE_SERVICE);
                            templateConfig.setServiceImpl(MY_TEMPLATE_SERVICE_IMPL);
                            break;
                        case "3":
                            String mapperModuleName = readUserInput("请输入Mapper接口与文件所属模块名称:", true);
                            // 设置 mapper 包名和路径
                            packageConfig.setMapper(mapperModuleName + ".biz.mapper." + folderName);
                            pathInfo.put(ConstVal.MAPPER_PATH, getParentPath(mapperModuleName, packageConfig.getParent(), packageConfig.getMapper()));
                            // 设置 xml 路径
                            packageConfig.setXml(mapperModuleName + ".biz.mapper." + folderName);
                            pathInfo.put(ConstVal.XML_PATH, getParentPath(mapperModuleName, packageConfig.getParent(), packageConfig.getXml()));
                            // 开启生成 mapper接口以及xml文件
                            templateConfig.setMapper(MY_TEMPLATE_MAPPER);
                            templateConfig.setXml(MY_TEMPLATE_XML);
                            break;
                        default:
                            System.out.println("无法识别的输入【" + selection + "】");
                            return;
                    }
                }
            }
        }
        packageConfig.setPathInfo(pathInfo);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://101.34.22.50:3306/blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&useAffectedRows=true");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver"); // Mysql8.0版本以下用 com.mysql.jdbc.Driver
        dsc.setUsername("blog");
        dsc.setPassword("blog123$%^");

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setServiceName("%sService"); // 默认是IXXXSERVICE的格式 有个I.. %s是占位符会用entity的名字填充
        gc.setFileOverride(true); // true会覆盖生成的文件慎用
        gc.setAuthor(AUTHOR);
        gc.setBaseColumnList(true); // XML 文件生成列名 sql 块
        gc.setBaseResultMap(true); // XML 文件生成BaseResultMap 块
        gc.setOpen(false);
        gc.setDateType(DateType.ONLY_DATE); // 时间类型使用 java.util.date

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true); // 使用 lombok 注解
        strategy.setRestControllerStyle(true); // controller使用RestController注解
        // 公共父类
        strategy.setSuperControllerClass("com.bytech.common.biz.controller.base.BasicController"); // controller 需要继承的父类 没有就删掉
        strategy.setSuperServiceImplClass("com.bytech.common.biz.service.base.BasicServiceImpl"); // serviceImpl 需要继承的父类
        strategy.setSuperMapperClass("com.bytech.common.biz.mapper.base.MyBatisBaseMapper"); // mapper 需要继承的父类
        strategy.setInclude(tableName);

        generator.setConfig(new ConfigBuilder(packageConfig, dsc, strategy, templateConfig, gc)); // 配置代码生成器
        generator.setCfg(injectionConfig); // 注入自定义属性
        generator.setTemplateEngine(new FreemarkerTemplateEngine()); // 设置模板引擎
        generator.execute();
    }

    /**
     * 通过模块名称、模块父路径、当前代码路径 返回一个用于存储生成代码文件的路径
     * 具体路径需要根据自己的项目文件结构自己拼接
     *
     * @param moduleName 模块名称 注意这里是指包名中的模块名称而不是blog-xx真实的模块名称，在生成返回路径时会通过此模块名拼接 /blog- 前缀
     * @param parent     父包名 本项目中固定为com.bytech
     * @param postFix    代码功能块后缀 例如 article模块中的文章（article）controller的路径为 article(模块名).biz.controller(固定的控制器路径).article(代表这是文章功能相关的)
     */
    private static String getParentPath(String moduleName, String parent, String postFix) {
        return rootProjectPath + "/blog-" + moduleName + "/src/main/java/" +
                (parent + "." + postFix).replaceAll("\\.", StringPool.BACK_SLASH + File.separator);
    }

    private static void showMainMenu() {
        initGenerator();
        List<String> legalInputs = ImmutableList.of("1", "2", "3", "4", "0");
        System.out.println("请输入您要生成的内容（生成多个可用,隔开），实体类为必选项会自动生成：");
        menuList.forEach(System.out::println);
        System.out.print("请输入您要生成的内容：");
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNext()) {
            String in = scanner.next();
            String[] userInputs = in.split(",");
            boolean goAfter = true;
            for (String s : userInputs) {
                if (!legalInputs.contains(s)) {
                    System.out.println("无法识别的输入信息【" + s + "】");
                    goAfter = false;
                } else {
                    userSelections.add(s);
                }
            }
            if (!goAfter) {
                showMainMenu();
            }
        } else {
            System.out.println("未检测到任何输入！");
            showMainMenu();
        }
    }

    /**
     * 初始化代码生成器
     */
    private static void initGenerator() {
        userSelections = Lists.newArrayList();
        templateConfig = new TemplateConfig();
        templateConfig.setXml(null);
        templateConfig.setEntity(null);
        templateConfig.setEntityKt(null);
        templateConfig.setService(null);
        templateConfig.setController(null);
        templateConfig.setServiceImpl(null);
        templateConfig.setMapper(null);
    }

    /**
     * 读取用户输入内容 判断是否合法时大小写敏感
     *
     * @param inline       提示信息是否在同一行
     * @param expectValues 期望输入的范围
     */
    public static String readUserInput(String tip, boolean inline, String... expectValues) {
        if (inline) {
            System.out.print(tip + (!ArrayUtils.isEmpty(expectValues) ? String.join("/", expectValues) : ""));
        } else {
            System.out.println(tip + (!ArrayUtils.isEmpty(expectValues) ? String.join("/", expectValues) : ""));
        }
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNext()) {
            String in = scanner.next();
            if (StringUtils.isBlank(in)) {
                System.out.println("未检测到任何输入信息！");
                return readUserInput(tip, inline, expectValues);
            } else if (!ArrayUtils.isEmpty(expectValues) && !Arrays.asList(expectValues).contains(in)) {
                System.out.println("输入信息非法！");
                return readUserInput(tip, inline, expectValues);
            } else {
                return in;
            }
        }
        System.out.println("未检测到任何输入信息！");
        return readUserInput(tip, inline, expectValues);
    }
}

